package leetcode.top100;

import java.util.HashMap;
import java.util.Map;

/**
 * 给定一个整数数组和一个整数 k，你需要找到该数组中和为 k 的连续的子数组的个数。
 * <p>
 * 示例 1 :
 * <p>
 * 输入:nums = [1,1,1], k = 2
 * <p>
 * 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
 * <p>
 * 说明 :
 * <p>
 * 数组的长度为 [1, 20,000]。
 * <p>
 * 数组中元素的范围是 [-1000, 1000] ，且整数 k 的范围是 [-1e7, 1e7]。
 *
 * @since 2019/12/23 0023 上午 9:58
 */
public class Code560_SubArraySum_和为K的子数组 {
    public static void main(String[] args) {
        int[] arr = {1, 1, -1, 1, 1};
        System.out.println(subarraySum2(arr, 2));
    }

    public static int subarraySum(int[] nums, int k) {
        if (nums == null || nums.length <= 0) return 0;
        return process4(nums, k);
    }

    /**
     * 方式5：dp
     * dp[i][j]表示从i到j的子序列的和
     * j >= i
     * dp[i][j] = dp[i][j-1] + nums[j]
     * dp[i][i] = nums[i]
     *
     * OJ超出内存
     */
    public static int subarraySum2(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int[][] dp = new int[nums.length][nums.length];
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
            dp[i][i] = nums[i];
            if (dp[i][i] == k) count++;
            //j >= i,j<i没有意义
            for (int j = i + 1; j < nums.length; j++) {
                dp[i][j] = dp[i][j - 1] + nums[j];
                if (dp[i][j] == k) count++;
            }
        }
        return count;
    }


    /**
     * 方式4：对方式3的优化。
     * <p>
     * 对数组进行遍历，需要存储从0开始到i位置的每个连续和。
     * <p>
     * 利用Map存储，K是和，V是和出现的次数。
     * <p>
     * 每次遍历到i，就查找nums[i]-k是否在map中出现过，
     * <p>
     * 若出现过(假设出现过两次，存储的位置分别是m1,m2)，
     * <p>
     * 表示从m1到i和从m2到i的连续和都是k。
     */
    private static int process4(int[] nums, int k) {
        int len = nums.length;
        int count = 0;
        int sum = 0;
        Map<Integer, Integer> map = new HashMap<>();
        //和为0的出现1次。初始位置
        map.put(0, 1);
        for (int i = 0; i < len; i++) {
            sum += nums[i];
            //利用Map，把查找O(n)的时间变为O(1)。从当前位置找前面是否符合，所以put行为放在后面
            if (map.containsKey(sum - k)) {
                //TODO 笔记上分析1，1，-1，1，1  k=2这个例子
                //从sum-k形成的位置开始重新计算，到当前位置i的和为k
                //因为这个过程中sum一直是连续和
                count += map.get(sum - k);
            }
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return count;
    }

    /**
     * 方式3:思路同方式2，用另一种表达方式
     */
    private static int process3(int[] nums, int k) {
        int len = nums.length;
        int count = 0;
        //sum[i]表示(-1,i-1)的总和
        //sum[j]-sum[i]表示(i,j-1)总和
        int[] sum = new int[len + 1];
        //注意要有sum[0]=0表示还未开始
        sum[0] = 0;
        for (int i = 1; i <= len; i++) {
            sum[i] = sum[i - 1] + nums[i - 1];
        }
        //i循环一次就相当于固定起始位置。
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j <= len; j++) {
                //(i,j-1)的总和。i相对于数组来说是从-1开始
                if (sum[j] - sum[i] == k) count++;
            }
        }
        return count;
    }

    /**
     * 方式2：固定起始点，终点全遍历。利用前后和关系
     * <p>
     * 方式1是对所有子数组求和，做了许多重复工作。
     * <p>
     * 譬如[i,j]求和sum(i,j),[i,j+1]再求和sum(i,j+1)
     * <p>
     * 可以在sum(i.j)基础上再加上num[j+1]即可。
     * <p>
     * 两次遍历，O(n²)
     */
    private static int process2(int[] nums, int k) {
        int len = nums.length;
        int count = 0;
        for (int i = 0; i < len; i++) {
            int sum = 0;
            //固定起始点，终点全遍历
            for (int j = i; j < len; j++) {
                if ((sum += nums[j]) == k) count++;
            }
        }
        return count;
    }

    /**
     * 方式1：穷举所有子数组，然后找满足的条件。
     * <p>
     * 穷举是O(N²),验证是O(n),总体O(n³)
     */
    private static int process1(int[] nums, int k) {
        int len = nums.length;
        int count = 0;
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                int sum = 0;
                for (int start = i; start <= j; start++) {
                    sum += nums[start];
                }
                if (sum == k) count++;
            }
        }
        return count;
    }

}
